不会吧?还有人不会Dfinity前端开发?
“日前,Uniswap对某些币种的前端限制引起了巨大争议。不论对错,从技术角度出发,Dfinity相比之下就是一个更好的全栈去中心化解决方案。本篇教程将从HelloWorld开始讲起,详细介绍如何在Dfinity上进行前端开发,以及如何使用官方提供的身份认证服务(identity.ic0.app)。”
PS: 阅读本文前需熟悉 dfx 开发工具的使用
在任意目录下输入如下命令,即可得到一个初始项目并部署
dfx new helloworld
dfx start --background & dfx deploy
在浏览器输入localhost:8000 或 localhost:8000/?canisterId=前端ID,即可访问你的项目,类似于下图
js代码如下
import { Actor, HttpAgent } from "@dfinity/agent";
import {
idlFactory as helloworld_idl,
canisterId as helloworld_id,
} from "dfx-generated/helloworld";
const agent = new HttpAgent();
const helloworld = Actor.createActor(helloworld_idl, {
agent,
canisterId: helloworld_id,
});
document.getElementById("clickMeBtn").addEventListener("click", async () => {
const name = document.getElementById("name").value.toString();
const greeting = await helloworld.greet(name);
document.getElementById("greeting").innerText = greeting;
});
这段代码先是创建了一个HttpAgent,用于发送网络请求。这里agent默认的host是你网络环境的地址,例如开发者在localhost:8000部署了前端,这里agent就会向localhost:8000发送请求。所以在使用dev server时一定要注意手动设置host,下文也会讲解dev server的使用。
如下是设置host的代码(生产环境中不要使用)
const agent = new HttpAgent({
host: "http://localhost:8000",
})
因为dfinity中的canister 使用的actor模型,前端在调用canister时需要先创建一个actor,这里可以简单理解为调用后端的工具。
接下来的就是创建actor的代码,这里需要三个参数,IDL,agnet,canisterId,agent就是我们刚才所创建的HttpAgent,另外两个参数将在如何调用第三方canister中介绍。
当我们完成了actor创建后就可以通过actor对后端的public method进行调用,注意这里的调用都是异步调用。对于异步调用的处理本篇教程就不再展开,比较推荐的方法是使用async/await语法。这段代码调用了greeting方法并返回了一段文本。
以上就是一个最简单的dfinity前端应用程序,了解这些过程就可以进行前后端的交互。
create-ic-app :create-ic-app使用了新型前端构建工具Vite,支持react、vue、typescript、vanilla等框架,并还在持续更新。另外,Vite的dev server响应速度比webpack快了不少,值得尝试。
dfinity-vue :vue脚手架,在一个分支上集成了vuetify,如需使用切换到vuetify分支即可。
cra-template-dfx :react脚手架,上次更新是七个月前,仅供参考不推荐使用。
Dfinity项目默认使用webpack打包,并自动生成了配置文件,我们需要做的其实非常少。
在讲webpack之前我们需要打开dfx.json来了解一下默认配置。
{
"canisters": {
...
//assets 是前端canister的名字,在dfinity中所有的代码都部署在canister中
"assets": {
//前端canister的dependency
"dependencies": [
"backend"
],
//frontend.entrypoint是前端文件的entry point,这个我们要在webpack中用到
"frontend": {
"entrypoint": "dist/index.html"
},
//source指定dist和src目录
"source": [
"dist/"
],
//指定canister的类型为前端canister
"type": "assets"
}
}
}
如何在项目中配置Typescript
首先是安装typescript并配置好tsconfig.json,安装ts-loader,这里不再展开。接下来找到webpack.config.js中这段被注释掉的代码,取消注释,即可在项目中使用typescript。
配置DevServer
事实上,dfx的开发体验差强人意,每当代码出现改动,就需要重新编译部署。这种体验对于前端开发来说如同噩梦,好在,我们还有dev server。
使用如下命令安装webpack-dev-server
npm i webpack-dev-server
安装完成后就可以通过webpack serve或是webpack-dev-server命令启动它,笔者的版本为3.11.2,对应的启动命令是webpack serve。
当然,也可以在package.json中配置dev选项,如图
如此,即可通过输入npm run dev 来启动Dev Server
接下来,在webpack中添加
pluginnew webpack.HotModuleReplacementPlugin()
然后添加dev server配置
devServer: {
hot: true,
}
即可实现修改文件后自动重新编译
内容可以参考kyle的博客
调用Canister需要两个参数,一个是IDL接口描述,另一个是Canister ID。
IDL是对Canister数据类型和接口的描述,在本地部署Canister时会自动生成一个canister.did.js文件。
export default ({ IDL }) => {
return IDL.Service({ 'greet' : IDL.Func([IDL.Text], [IDL.Text], []) });
};
export const init = ({ IDL }) => { return []; };
Hello World的IDL如下
import { idlFactory } from "dfx-generated/helloworld";
//或
import { idlFactory } from './helloworld.did.js';
导入IDL
如果是第三方Canister你可以到ic.rocks找到对应的IDL文件,以Identity服务为例
找到对应的Canister
选择javascript,新建一个identity.did.js复制粘贴进去(ts对应identity.did.d.ts)
// src/declarations/identity/index.js
import { Actor, HttpAgent } from "@dfinity/agent";
// Imports and re-exports candid interface
import { idlFactory } from './identity.did.js';
export { idlFactory } from './identity.did.js';
// CANISTER_ID is replaced by webpack based on node environment
export const canisterId = process.env.IDENTITY_CANISTER_ID;
/**
*
* @param {string | import("@dfinity/principal").Principal} canisterId Canister ID of Agent
* @param {{agentOptions?: import("@dfinity/agent").HttpAgentOptions; actorOptions?: import("@dfinity/agent").ActorConfig}} [options]
* @return {import("@dfinity/agent").ActorSubclass<import("./identity.did.js")._SERVICE>}
*/
export const createActor = (canisterId, options) => {
const agent = new HttpAgent({ ...options?.agentOptions });
// Fetch root key for certificate validation during development
if(process.env.NODE_ENV !== "production") {
agent.fetchRootKey().catch(err=>{
console.warn("Unable to fetch root key. Check to ensure that your local replica is running");
console.error(err);
});
}
// Creates an actor with using the candid interface and the HttpAgent
return Actor.createActor(idlFactory, {
agent,
canisterId,
...options?.actorOptions,
});
};
/**
* A ready-to-use agent for the identity canister
* @type {import("@dfinity/agent").ActorSubclass<import("./identity.did.js")._SERVICE>}
*/
export const identity = createActor(canisterId);
创建一个用于调用identity服务的actor
如果只有candid文件,可以通过dfx工具生成一份did.js,不过这种方式比较繁琐,还有一种更简单的方式是通过官方提供的didc工具
自动安装脚本:(复制保存为sh文件运行)
unameOut="$(uname -s)"
case "${unameOut}" in
Linux*) machine=Linux;;
Darwin*) machine=Mac;;
*) machine="UNKNOWN:${unameOut}"
esac
release=$(curl --silent "https://api.github.com/repos/dfinity/candid/releases/latest" | grep -e '"tag_name"' | cut -c 16-25)
if [ ${machine} = "Mac" ]
then
echo "Downloading didc for Mac to ~/bin/didc"
curl -fsSL https://github.com/dfinity/candid/releases/download/${release}/didc-macos > /usr/local/bin/didc
elif [ ${machine} = "Linux" ]
then
echo "Downloading didc for Linux to ~/bin/didc"
curl -fsSL https://github.com/dfinity/candid/releases/download/${release}/didc-linux64 > ~/bin/didc
else
echo "Could not detect a supported operating system. Please note that didc is currently only supported for Mac and Linux"
fi
date
安装完成后输入
didc bind ./identity.did -t js > ./identity.did.js
didc bind ./identity.did -t ts > ./identity.did.d.ts //ts版本
就可以生成对应的did.js文件或did.d.ts文件
Internet Identity(简称II) 是dfinity官方推出的DID服务,基于WebAuth。在Dfinity上的应用一般会支持使用II登录。
如果想要在本地的开发环境调试II,需要把Ineternet Identity clone 到本地,并部署到你本地用于开发的dfx网络环境中。
git clone git@github.com:dfinity/internet-identity.git
cd internet-identity
dfx start --clean
这里推荐使用--clean参数启动,因为II服务的默认Canister ID与本地钱包的默认ID相同,所以先清扫一下环境避免冲突。注意因为本地能启动多个dfx网络,一定要保证II部署的网络与项目的网络相同(端口相同)。
npm install
II_ENV=development dfx deploy --no-wallet --argument '(null)'
这样就完成了II的本地部署
打开package.json确保你安装了
@dfinity/auth-client、
@dfinity/authentication
下面是前端调用II服务的代码
const init = async () => {
authClient = await AuthClient.create();
await authClient.login({
maxTimeToLive: BigInt("0x7f7f7f7f7f"),
identityProvider:
"http://localhost:8000/?canisterId=rwlgt-iiaaa-aaaaa-aaaaa-cai"
onSuccess: async () => {
handleAuthenticated(authClient);
},
});
}
async function handleAuthenticated(authClient) {
identity = await authClient.getIdentity();
const agent = new HttpAgent({
identity: identity,
host: "http://localhost:8000",
});
//本地开发时需要获取Root Key
if(process.env.NODE_ENV !== "production") await agent.fetchRootKey();
let actor = Actor.createActor(idlFactory, {
agent,
canisterId: canisterId,
});
const greeting = await actor.greet(name);
}